import * as THREE from 'three';

import Stats from 'three/examples/jsm/libs/stats.module.js';
import { GUI } from 'three/examples/jsm/libs/dat.gui.module.js';
import { TWEEN } from 'three/examples/jsm/libs/tween.module.min.js';
import { OrbitControls } from 'three/examples/jsm/controls/experimental/CameraControls.js';

class createScene {
  constructor( renderer, camera, scene, geometry ) {
    if (! scene) {
      scene = new THREE.Scene();
      scene.background = new THREE.Color();

      let material;
      if (geometry.name === 'BoxGeometry') {

        const textures = getTexturesFromAtlasFile( require('../../assets/transition/sun_temple_stripe.jpg'), 6 );

        const materials = [];

        for (let i = 0; i < 6; i ++) materials.push( new THREE.MeshBasicMaterial( { map: textures[i], side: THREE.DoubleSide } ) );

        geometry = new THREE.BoxGeometry( 100, 100, - 100 );
        material = materials;

      } else {

        geometry = new THREE.SphereGeometry( 100, 60, 40 );
        geometry.scale( - 1, 1, 1 );

        const texture = new THREE.TextureLoader().load( require('../../assets/transition/2294472375_24a3b8ef46_o.jpg') );
        material = new THREE.MeshBasicMaterial( { map: texture, side: THREE.DoubleSide } );

      }
      const mesh = new THREE.Mesh( geometry, material );
      scene.add( mesh );
    }
    const dPR = window.devicePixelRatio;
    const renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
    this.renderTarget = new THREE.WebGLRenderTarget( container.offsetWidth * dPR, container.offsetHeight * dPR, renderTargetParameters );

    this.render = ( delta, transition = false ) => {
      if (transition) {
        renderer.setRenderTarget( this.renderTarget );
        renderer.clear();
        renderer.render( scene, camera );
      } else {
        renderer.setRenderTarget( null );
        renderer.render( scene, camera );
      }
    };
  };
}

class Transitions {
  constructor( renderer, fromScene, toScene ) {
    const width = renderer.domElement.offsetWidth, height = renderer.domElement.offsetHeight;
    this._scene = new THREE.Scene();
    this._orthographicCamera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, - 10, 10 );
    this._renderer = renderer;

    this.specialEffectsMap = [];

    this.useTexture = true;
    this.transition = 0;
    this.threshold = 0.3;


    const loader = new THREE.TextureLoader();
    for (let i = 0; i < 6; i ++) this.specialEffectsMap[i] = loader.load(  require(`../../assets/transition/transition${i+1}.png`));


    const material = new THREE.ShaderMaterial( {
      uniforms: {
        // tDiffuse1: { value: fromScene.fbo.texture },
        // tDiffuse2: { value: toScene.fbo.texture },
        tDiffuse1: { value: toScene.renderTarget.texture },
        tDiffuse2: { value: fromScene.renderTarget.texture },
        mixRatio: { value: 0.0 },
        threshold: { value: this.threshold },
        useTexture: { value: true },
        tMixTexture: { value: this.specialEffectsMap[0] }
      },
      vertexShader: `
				varying vec2 vUv;
				void main() {
				vUv = vec2( uv.x, uv.y );
					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
				}
			`,
      fragmentShader: `
				uniform float mixRatio;
				uniform sampler2D tDiffuse1;
				uniform sampler2D tDiffuse2;
				uniform sampler2D tMixTexture;
				uniform int useTexture;
				uniform float threshold;
				varying vec2 vUv;
				void main() {
				  	vec4 texel1 = texture2D( tDiffuse1, vUv );
				  	vec4 texel2 = texture2D( tDiffuse2, vUv );
				  	if (useTexture==1) {
						vec4 transitionTexel = texture2D( tMixTexture, vUv );
						float r = mixRatio * (1.0 + threshold * 2.0) - threshold;
						float mixf=clamp((transitionTexel.r - r)*(1.0/threshold), 0.0, 1.0);
						gl_FragColor = mix( texel1, texel2, mixf );
				  	} else {
						gl_FragColor = mix( texel2, texel1, mixRatio );
				  	}
				}`
    } );

    const geometry = new THREE.PlaneGeometry( width, height );
    const mesh = new THREE.Mesh( geometry, material );
    this._scene.add( mesh );

    this.start = ( value ) => {
      new TWEEN.Tween( this ).to( { transition: value }, 1000 ).onUpdate( () => {
        console.log( this.transition );
      } ).start();
    };

    this.setTextureThreshold = ( value ) => material.uniforms.threshold.value = value;

    this.useTexture = ( value ) => material.uniforms.useTexture.value = value ? 1 : 0;

    this.setTexture = ( i ) => material.uniforms.tMixTexture.value = this.specialEffectsMap[i];

    this.setScene = ( fromTexture, toTexture ) => {
      material.uniforms.tDiffuse1.value = toTexture;
      material.uniforms.tDiffuse2.value = fromTexture;
    };

    this.render = ( delta ) => {

      TWEEN.update();

      if (this.transition === 0) {

        fromScene.render( delta, false );

      } else if (this.transition === 1) {

        toScene.render( delta, false );

      } else {
        material.uniforms.mixRatio.value = this.transition;

        fromScene.render( delta, true );
        toScene.render( delta, true );

        this._renderer.setRenderTarget( null );
        this._renderer.clear();
        this._renderer.render( this._scene, this._orthographicCamera );

      }
    };
  }
}

let container, stats, orbitControls;
let renderer;
let transition;

const width = window.innerWidth;
const height = window.innerHeight;
const orthographicCamera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, - 10, 10 );
const transitionParams = { 'useTexture': true, 'transition': 0, 'texture': 5, 'cycle': true, 'animate': true, 'threshold': 0.3 };

const clock = new THREE.Clock();

const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, .1, 100000 );
camera.position.z = .200;
init();
animate();

function init() {

  initGUI();

  container = document.getElementById( 'container' );

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  container.appendChild( renderer.domElement );

  stats = new Stats();
  container.appendChild( stats.dom );

  const geometryA = new THREE.BoxGeometry( 200, 200, 200 );
  geometryA.name = 'BoxGeometry';
  const geometryB = new THREE.BoxBufferGeometry( 2, 2, 2 );
  // const sceneA = new FXScene( geometryA, new THREE.Vector3( 0, - 0.4, 0 ), 0xffffff );
  // const sceneB = new FXScene( geometryB, new THREE.Vector3( 0, 0.2, 0.1 ), 0x000000 );
  const sceneA = new createScene( renderer, camera, undefined, geometryA );
  const sceneB = new createScene( renderer, camera, undefined, geometryB );

  // transition = new Transition( sceneA, sceneB );
  window.transition = transition = new Transitions( renderer, sceneA, sceneB );
  orbitControls = new OrbitControls( camera, renderer.domElement );
  orbitControls.enableDamping = true;

  window.addEventListener( 'resize', function() {

    const width = window.innerWidth;
    const height = window.innerHeight;

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize( width, height );

  }, false );

}

function animate() {

  requestAnimationFrame( animate );
  render();
  stats.update();

}

function initGUI() {

  const gui = new GUI();
  gui.add( transitionParams, 'animate' );
  gui.add( transitionParams, 'transition', 0, 1, 0.01 ).listen();
  gui.add( transitionParams, 'useTexture' ).onChange( function( value ) {

    transition.useTexture( value );

  } );
  gui.add( transitionParams, 'texture', { Perlin: 0, Squares: 1, Cells: 2, Distort: 3, Gradient: 4, Radial: 5 } ).onChange( function( value ) {

    transition.setTexture( value );

  } ).listen();
  gui.add( transitionParams, 'cycle' );
  gui.add( transitionParams, 'threshold', 0, 1, 0.01 ).onChange( function( value ) {

    transition.setTextureThreshold( value );

  } );

}

function getTexturesFromAtlasFile( atlasImgUrl, tilesNum ) {

  const textures = [];

  for (let i = 0; i < tilesNum; i ++) {

    textures[i] = new THREE.Texture();

  }

  new THREE.ImageLoader()
    .load( atlasImgUrl, ( image ) => {

      let canvas, context;
      const tileWidth = image.height;

      for (let i = 0; i < textures.length; i ++) {

        canvas = document.createElement( 'canvas' );
        context = canvas.getContext( '2d' );
        canvas.height = tileWidth;
        canvas.width = tileWidth;
        context.drawImage( image, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth );
        textures[i].image = canvas;
        textures[i].needsUpdate = true;

      }

    } );

  return textures;

}

function render() {
  orbitControls.update();
  transition.render( clock.getDelta() );

}

function FXScene( geometry, rotationSpeed, clearColor ) {

  this.clearColor = clearColor;

  // Setup scene
  const scene = new THREE.Scene();
  scene.background = new THREE.Color( clearColor );

  let material;
  if (geometry.name === 'BoxGeometry') {

    const textures = getTexturesFromAtlasFile( require('../../assets/transition/sun_temple_stripe.jpg'), 6 );

    const materials = [];

    for (let i = 0; i < 6; i ++) materials.push( new THREE.MeshBasicMaterial( { map: textures[i], side: THREE.DoubleSide } ) );

    geometry = new THREE.BoxGeometry( 100, 100, - 100 );
    material = materials;

  } else {

    geometry = new THREE.SphereGeometry( 100, 60, 40 );
    geometry.scale( - 1, 1, 1 );

    const texture = new THREE.TextureLoader().load( require('../../assets/transition/2294472375_24a3b8ef46_o.jpg')  );
    material = new THREE.MeshBasicMaterial( { map: texture, side: THREE.DoubleSide } );

  }

  const light = new THREE.SpotLight( 0xffffff, 1.5 );
  light.position.set( 0, 500, 2000 );
  scene.add( light );


  const mesh = new THREE.Mesh( geometry, material );
  scene.add( mesh );

  const dPR = window.devicePixelRatio;
  const renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
  this.renderTarget = new THREE.WebGLRenderTarget( container.offsetWidth * dPR, container.offsetHeight * dPR, renderTargetParameters );
  console.log( container.offsetWidth * dPR, container.offsetHeight * dPR );

  this.render = function( delta, rtt ) {

    if (rtt) {

      renderer.setRenderTarget( this.renderTarget );
      renderer.clear();
      renderer.render( scene, camera );

    } else {

      renderer.setRenderTarget( null );
      renderer.render( scene, camera );

    }

  };

}

function Transition( sceneA, sceneB ) {

  const scene = new THREE.Scene();

  const textures = [];

  const loader = new THREE.TextureLoader();

  for (let i = 0; i < 6; i ++) textures[i] = loader.load( require(`../../assets/transition/transition${i+1}.png`) );

  const material = new THREE.ShaderMaterial( {
    uniforms: {
      tDiffuse1: { value: null },
      tDiffuse2: { value: null },
      mixRatio: { value: 0.0 },
      threshold: { value: transitionParams.threshold },
      useTexture: { value: true },
      tMixTexture: { value: textures[0] }
    },
    vertexShader: `
				varying vec2 vUv;
				void main() {
				vUv = vec2( uv.x, uv.y );
					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
				}
			`,
    fragmentShader: `
				uniform float mixRatio;
				uniform sampler2D tDiffuse1;
				uniform sampler2D tDiffuse2;
				uniform sampler2D tMixTexture;
				uniform int useTexture;
				uniform float threshold;
				varying vec2 vUv;
				void main() {
				  	vec4 texel1 = texture2D( tDiffuse1, vUv );
				  	vec4 texel2 = texture2D( tDiffuse2, vUv );
				  	if (useTexture==1) {
						vec4 transitionTexel = texture2D( tMixTexture, vUv );
						float r = mixRatio * (1.0 + threshold * 2.0) - threshold;
						float mixf=clamp((transitionTexel.r - r)*(1.0/threshold), 0.0, 1.0);
						gl_FragColor = mix( texel1, texel2, mixf );
				  	} else {
						gl_FragColor = mix( texel2, texel1, mixRatio );
				  	}
				}`
  } );


  const geometry = new THREE.PlaneGeometry( container.offsetWidth, container.offsetHeight );
  const mesh = new THREE.Mesh( geometry, material );
  scene.add( mesh );

  material.uniforms.tDiffuse1.value = sceneA.renderTarget.texture;
  material.uniforms.tDiffuse2.value = sceneB.renderTarget.texture;

  new TWEEN.Tween( transitionParams ).to( { transition: 1 }, 2000 ).repeat( Infinity ).delay( 2000 ).yoyo( true ).start();

  this.needsTextureChange = false;

  this.setTextureThreshold = ( value ) => material.uniforms.threshold.value = value;

  this.useTexture = ( value ) => material.uniforms.useTexture.value = value ? 1 : 0;

  this.setTexture = ( i ) => material.uniforms.tMixTexture.value = textures[i];

  this.render = function( delta ) {

    if (transitionParams.animate) {

      TWEEN.update();

      if (transitionParams.cycle) {

        if (transitionParams.transition === 0 || transitionParams.transition === 1) {

          if (this.needsTextureChange) {

            transitionParams.texture = ( transitionParams.texture + 1 ) % textures.length;
            material.uniforms.tMixTexture.value = textures[transitionParams.texture];
            this.needsTextureChange = false;

          }

        } else {

          this.needsTextureChange = true;

        }

      } else {

        this.needsTextureChange = true;

      }

    }


    // Prevent render both scenes when it's not necessary
    if (transitionParams.transition === 0) {

      sceneB.render( delta, false );

    } else if (transitionParams.transition === 1) {

      sceneA.render( delta, false );

    } else {

      material.uniforms.mixRatio.value = transitionParams.transition;
      // When 0 < transition < 1 render transition between two scenes
      sceneA.render( delta, true );
      sceneB.render( delta, true );

      renderer.setRenderTarget( null );
      renderer.clear();
      renderer.render( scene, orthographicCamera );

    }

  };

}

